﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
#if NET40
namespace System.Reflection
{
    [Serializable]
    [ComVisible(true)]
    public abstract class TypeInfo : Type, IReflectableType
    {
        public virtual Type[] GenericTypeParameters
        {
            get
            {
                if (IsGenericTypeDefinition)
                {
                    return GetGenericArguments();
                }

                return Type.EmptyTypes;
            }
        }

        public virtual IEnumerable<ConstructorInfo> DeclaredConstructors
        {
            get
            {
                return GetConstructors(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            }
        }

        public virtual IEnumerable<EventInfo> DeclaredEvents
        {
            get
            {
                return GetEvents(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            }
        }

        public virtual IEnumerable<FieldInfo> DeclaredFields
        {
            get
            {
                return GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            }
        }

        public virtual IEnumerable<MemberInfo> DeclaredMembers
        {
            get
            {
                return GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            }
        }

        public virtual IEnumerable<MethodInfo> DeclaredMethods
        {
            get
            {
                return GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            }
        }

        public virtual IEnumerable<TypeInfo> DeclaredNestedTypes
        {
            get
            {
                Type[] nestedTypes = GetNestedTypes(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
                foreach (Type type in nestedTypes)
                {
                    yield return type.GetTypeInfo();
                }
            }
        }

        public virtual IEnumerable<PropertyInfo> DeclaredProperties
        {
            get
            {
                return GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            }
        }

        public virtual IEnumerable<Type> ImplementedInterfaces
        {
            get
            {
                return GetInterfaces();
            }
        }

        internal TypeInfo()
        {
        }

        public TypeInfo GetTypeInfo()
        {
            return this;
        }

        public virtual Type AsType()
        {
            return this;
        }

        public virtual bool IsAssignableFrom(TypeInfo typeInfo)
        {
            if (typeInfo == null)
            {
                return false;
            }

            if (this == typeInfo)
            {
                return true;
            }

            if (typeInfo.IsSubclassOf(this))
            {
                return true;
            }

            if (base.IsInterface)
            {
                return typeInfo.ImplementInterface(this);
            }

            if (IsGenericParameter)
            {
                Type[] genericParameterConstraints = GetGenericParameterConstraints();
                for (int i = 0; i < genericParameterConstraints.Length; i++)
                {
                    if (!genericParameterConstraints[i].IsAssignableFrom(typeInfo))
                    {
                        return false;
                    }
                }

                return true;
            }

            return false;
        }

        public virtual EventInfo GetDeclaredEvent(string name)
        {
            return GetEvent(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        }
        public virtual FieldInfo GetDeclaredField(string name)
        {
            return GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        }

        public virtual MethodInfo GetDeclaredMethod(string name)
        {
            return GetMethod(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        }

        public virtual IEnumerable<MethodInfo> GetDeclaredMethods(string name)
        {
            MethodInfo[] methods = GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (MethodInfo methodInfo in methods)
            {
                if (methodInfo.Name == name)
                {
                    yield return methodInfo;
                }
            }
        }

        public virtual TypeInfo GetDeclaredNestedType(string name)
        {
            Type nestedType = GetNestedType(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            if (nestedType == null)
            {
                return null;
            }

            return nestedType.GetTypeInfo();
        }
        public virtual PropertyInfo GetDeclaredProperty(string name)
        {
            return GetProperty(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        }
    }
    public interface IReflectableType
    {
        TypeInfo GetTypeInfo();
    }
    public static class IntrospectionExtensions
    {
        public static TypeInfo GetTypeInfo(this Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }

            return ((IReflectableType)type)?.GetTypeInfo();
        }
    }
}
#endif
